// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Universe.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.Forge
{
    #region Usings

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;

    using Org.OpenTrader.Framework.Enums;
    using Org.OpenTrader.Framework.Forge.DataCache;
    using Org.OpenTrader.Framework.Forge.Enums;
    using Org.OpenTrader.Framework.Forge.Interfaces;

    #endregion

    /// <summary>
    /// The universe.
    /// </summary>
    public class Universe : IUniverse
    {
        #region Constants and Fields

        /// <summary>
        /// The identity.
        /// </summary>
        private static readonly Identity Identity = Identity.Create(MethodBase.GetCurrentMethod());

        /// <summary>
        /// The dictionary provider.
        /// </summary>
        private readonly IDictionaryProvider dictionaryProvider;

        /// <summary>
        /// The expos.
        /// </summary>
        private readonly IDictionary<ETradingScope, UniverseExpo> expos = new Dictionary<ETradingScope, UniverseExpo>();

        /// <summary>
        /// The feed provider.
        /// </summary>
        private readonly IFeedProvider feedProvider;

        /// <summary>
        /// The instruments for each referential.
        /// </summary>
        private readonly IDictionary<IReferential, IList<IInstrument>> instruments = new Dictionary<IReferential, IList<IInstrument>>();

        /// <summary>
        /// The intruments views per instrument code 
        /// That is all the instruments ever created for a given instrument code (ex: DELL US Equity)
        /// Aggregated per trading scope
        /// </summary>
        private readonly IDictionary<string, IInstrumentView> intrumentsViews = new Dictionary<string, IInstrumentView>();

        /// <summary>
        /// The order provider.
        /// </summary>
        private readonly IOrderProvider orderProvider;

        /// <summary>
        /// The referentials.
        /// </summary>
        private readonly IDictionary<object, IReferential> referentials = new Dictionary<object, IReferential>();

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Universe"/> class.
        /// </summary>
        /// <param name="dicoProvider">
        /// The dico provider.
        /// </param>
        /// <param name="feedProvider">
        /// The feed provider.
        /// </param>
        /// <param name="orderProvider">
        /// The order provider.
        /// </param>
        public Universe(IDictionaryProvider dicoProvider, IFeedProvider feedProvider, IOrderProvider orderProvider)
        {
            Logging.Log(Identity, ELogLevel.Debug, () => string.Format("Creating Universe"));

            // Dictionary Provider
            if (dicoProvider != null)
            {
                Logging.Log(Identity, ELogLevel.Debug, () => string.Format("Creating Universe : adding Dictionary Provider : {0}", dicoProvider.ToString()));
                this.dictionaryProvider = dicoProvider;
            }
            else
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("Creating Universe : Unknown Dictionary Provider "));
            }

            // Feed Provider
            if (feedProvider != null)
            {
                Logging.Log(Identity, ELogLevel.Debug, () => string.Format("Creating Universe : adding Feed Provider: {0}", feedProvider.ToString()));
                this.feedProvider = feedProvider;
            }
            else
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("Creating Universe : Unknown Feed Provider"));
            }

            // Order Provider
            if (orderProvider != null)
            {
                Logging.Log(Identity, ELogLevel.Debug, () => string.Format("Creating Universe : adding Order Provider: {0}", orderProvider.ToString()));
                this.orderProvider = orderProvider;
            }
            else
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("Creating Universe : Unknown Order Provider"));
            }

            // Expo
            foreach (var scope in Enum.GetValues(typeof(ETradingScope)))
            {
                Logging.Log(Identity, ELogLevel.Debug, () => string.Format("Creating Universe : Adding Expo {0} : ", (ETradingScope)scope));
                this.expos[(ETradingScope)scope] = new UniverseExpo((ETradingScope)scope);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Universe"/> class.
        /// </summary>
        public Universe()
        {
            Logging.Log(Identity, ELogLevel.Debug, () => string.Format("Creating default Universe"));

            // Expo
            foreach (var scope in Enum.GetValues(typeof(ETradingScope)))
            {
                Logging.Log(Identity, ELogLevel.Debug, () => string.Format("Creating default Universe : Adding Expo {0} : ", (ETradingScope)scope));
                this.expos[(ETradingScope)scope] = new UniverseExpo((ETradingScope)scope);
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets InstrumentViews.
        /// </summary>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public string[] InstrumentViews
        {
            get
            {
                return this.intrumentsViews.Keys.ToArray();
            }
        }

        #endregion

        #region Indexers

        /// <summary>
        /// The this.
        /// </summary>
        /// <param name="g">
        /// The g.
        /// </param>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public IInstrumentView this[string code]
        {
            get
            {
                if (code == null)
                {
                    return null;
                }

                if (! this.intrumentsViews.ContainsKey(code))
                {
                    return null;
                }

                return this.intrumentsViews[code];
            }
        }

        /// <summary>
        /// The this.
        /// </summary>
        /// <param name="r">
        /// The r.
        /// </param>
        public IInstrument[] this[IReferential r]
        {
            get
            {
                if (r == null || !this.instruments.ContainsKey(r))
                {
                    return new IInstrument[] { };
                }

                return this.instruments[r].ToArray();
            }
        }

        /// <summary>
        /// The this.
        /// </summary>
        /// <param name="scope">
        /// The scope.
        /// </param>
        public UniverseExpo this[ETradingScope scope]
        {
            get
            {
                return this.expos[scope];
            }
        }

        #endregion

        #region Implemented Interfaces

        #region IDisposable

        /// <summary>
        /// The dispose.
        /// </summary>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public void Dispose()
        {
        }

        #endregion

        #region IUniverse

        /// <summary>
        /// The create instrument.
        /// </summary>
        /// <param name="referential">
        /// The referential.
        /// </param>
        /// <param name="code">
        /// The code.
        /// </param>
        /// <param name="tradingScope">
        /// The trading scope.
        /// </param>
        /// <returns>
        /// </returns>
        public Status<IInstrument> CreateInstrument(IReferential referential, string code, ETradingScope tradingScope)
        {
            // Check Referential
            if (referential == null)
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("CreateInstruments : Invalid Referential"));
                return Status.Failure<IInstrument>(Identity, "CreateInstrument: Referential is invalid");
            }

            // Check if the referential is known
            if (!this.referentials.Values.Contains(referential))
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("CreateInstrument : Unknown Referential"));
                return Status.Failure<IInstrument>(Identity, "CreateInstrument: Referential is unknown");
            }

            // If capability is not enabled, don't do anything
            if (this.dictionaryProvider == null)
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("CreateInstrument : Unknown Dictionary Provider"));
                return Status.Failure<IInstrument>(Identity, "CreateInstrument: No dictionary provider");
            }

            // Lookup instrument and encapsulate it into a Instrument
            var lookupstatus = this.dictionaryProvider.LookupInstrument<IInstrumentStaticData>(code, tradingScope);
            if (lookupstatus)
            {
                var staticData = lookupstatus.NestedObject;
                return this.CreateInstrument(referential, staticData.ID);
            }

            // Failure
            Logging.Log(Identity, ELogLevel.Error, () => string.Format("CreateInstrument : Cannot Create Instrument : {0} ({1})", code, tradingScope));
            return Status.Failure<IInstrument>(Identity, "CreateInstrument: Failure", lookupstatus);
        }

        /// <summary>
        /// The create instruments.
        /// </summary>
        /// <param name="referential">
        /// The referential.
        /// </param>
        /// <param name="predicate">
        /// The predicate.
        /// </param>
        /// <returns>
        /// </returns>
        public Status<IInstrument[]> CreateInstruments(IReferential referential, Expression<Func<IInstrumentStaticData, bool>> predicate)
        {
            // Check Referential
            if (referential == null)
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("CreateInstruments : Invalid Referential"));
                return Status.Failure<IInstrument[]>(Identity, "CreateInstruments: Referential is invalid");
            }

            // Check if the referential is known
            if (!this.referentials.Values.Contains(referential))
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("CreateInstruments : Unknown Referential"));
                return Status.Failure<IInstrument[]>(Identity, "CreateInstruments: Referential is unknown");
            }

            // If capability is not enabled, don't do anything
            if (this.dictionaryProvider == null)
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("CreateInstruments : Unknown Dictionary Provider"));
                return Status.Failure<IInstrument[]>(Identity, "CreateInstruments: No dictionary provider");
            }

            // Lookup instruments and encapsulate them into a Instrument
            IList<IInstrument> instrumentsList = new List<IInstrument>();
            var lookupstatus = this.dictionaryProvider.LookupInstruments(predicate);
            if (lookupstatus)
            {
                var staticDatas = lookupstatus.NestedObject;
                foreach (var staticData in staticDatas)
                {
                    var status = this.CreateInstrument(referential, staticData.ID);
                    if (status)
                    {
                        instrumentsList.Add(status.NestedObject);
                    }
                }

                // Success
                Logging.Log(
                    Identity, ELogLevel.Debug, () => string.Format("CreateInstruments : Instruments created for referential : {0}", referential.ToString()));
                return Status.Success<IInstrument[]>(Identity, instrumentsList.ToArray());
            }

            // Failure
            Logging.Log(Identity, ELogLevel.Error, () => string.Format("CreateInstruments : Cannot Create Instruments"));
            return Status.Failure<IInstrument[]>(Identity, "CreateInstruments: Failure", lookupstatus);
        }

        /// <summary>
        /// The get referential.
        /// </summary>
        /// <param name="owner">
        /// The owner.
        /// </param>
        /// <returns>
        /// </returns>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public IReferential Referential(object owner)
        {
            // Null owner is not possible
            if (owner == null)
            {
                return null;
            }

            // Doesn't exist yet -> create
            if (! this.referentials.ContainsKey(owner))
            {
                var r = new Referential(this, this.dictionaryProvider, this.feedProvider, this.orderProvider);
                this.referentials[owner] = r;
                this.instruments[r] = new List<IInstrument>();
            }

            // Here it is
            return this.referentials[owner];
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// The create instrument.
        /// </summary>
        /// <param name="referential">
        /// The referential.
        /// </param>
        /// <param name="instrumentStaticDataID">
        /// The static data.
        /// </param>
        /// <returns>
        /// </returns>
        private Status<IInstrument> CreateInstrument(IReferential referential, Guid instrumentStaticDataID)
        {
            // Check Referential
            if (referential == null)
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("CreateInstrument: Referential is invalid"));
                return Status.Failure<IInstrument>(Identity, "CreateInstrument: Referential is invalid");
            }

            // lookup the static data
            var instrumentStaticData = Singleton<DC>.Instance.GetInstrumentStaticDataFromCache(instrumentStaticDataID);

            // Check Instrument Static Data
            if (instrumentStaticData == null)
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("CreateInstrument: Invalid Instrument Static Data"));
                return Status.Failure<IInstrument>(Identity, "CreateInstrument: Invalid Instrument Static Data");
            }

            // Create the Instrument instance
            var instrument = new Instrument(instrumentStaticDataID, referential);

            // Add to the instruments list of this referential
            this.instruments[referential].Add(instrument);

            // Add a new instrument view for the instrument code if none is already present
            if (!this.intrumentsViews.ContainsKey(instrumentStaticData.Code))
            {
                this.intrumentsViews[instrumentStaticData.Code] = new InstrumentView();
            }

            // Add the instrument to the instrument view
            this.intrumentsViews[instrumentStaticData.Code].Add(instrument);

            // All done
            Logging.Log(
                Identity, 
                ELogLevel.Debug, 
                () => string.Format("CreateInstrument : Instrument Created : {0} ({1})", instrumentStaticData.Code, instrumentStaticData.TradingScope));
            return Status.Success<IInstrument>(Identity, instrument);
        }

        #endregion
    }
}